/*
 * Copyright (C) 2015 - 2020, IBEROXARXA SERVICIOS INTEGRALES, S.L.
 * Copyright (C) 2015 - 2020, Jaume Olivé Petrus (jolive@whitecatboard.org)
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *     * The WHITECAT logotype cannot be changed, you can remove it, but you
 *       cannot change it in any way. The WHITECAT logotype is:
 *
 *          /\       /\
 *         /  \_____/  \
 *        /_____________\
 *        W H I T E C A T
 *
 *     * Redistributions in binary form must retain all copyright notices printed
 *       to any local or remote output device. This include any reference to
 *       Lua RTOS, whitecatboard.org, Lua, and other copyright notices that may
 *       appear in the future.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Lua RTOS, a tool for make a LFS file system image
 *
 */

#include "lfs.h"

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <dirent.h>
#include <sys/types.h>

static struct lfs_config cfg;
static lfs_t lfs;
static uint8_t *data;

static int lfs_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
{
    memcpy(buffer, data + (block * c->block_size) + off, size);
    return 0;
}

static int lfs_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
    memcpy(data + (block * c->block_size) + off, buffer, size);
    return 0;
}

static int lfs_erase(const struct lfs_config *c, lfs_block_t block)
{
    memset(data + (block * c->block_size), 0, c->block_size);
    return 0;
}

static int lfs_sync(const struct lfs_config *c)
{
    return 0;
}

static void create_dir(char *src)
{
    char *path;
    int ret;

    path = strchr(src, '/');
    if (path) {
        fprintf(stdout, "%s\r\n", path);

        if ((ret = lfs_mkdir(&lfs, path)) < 0) {
            fprintf(stderr, "can't create directory %s: error=%d\r\n", path, ret);
            exit(1);
        }
    }
}

static void create_file(char *src)
{
    char *path;
    int ret;

    path = strchr(src, '/');
    if (path) {
        fprintf(stdout, "%s\r\n", path);

        // Open source file
        FILE *srcf = fopen(src, "rb");
        if (!srcf) {
            fprintf(stderr, "can't open source file %s: errno=%d (%s)\r\n", src, errno, strerror(errno));
            exit(1);
        }

        // Open destination file
        lfs_file_t dstf;
        if ((ret = lfs_file_open(&lfs, &dstf, path, LFS_O_WRONLY | LFS_O_CREAT)) < 0) {
            fprintf(stderr, "can't open destination file %s: error=%d\r\n", path, ret);
            exit(1);
        }

        char c = fgetc(srcf);
        while (!feof(srcf)) {
            ret = lfs_file_write(&lfs, &dstf, &c, 1);
            if (ret < 0) {
                fprintf(stderr, "can't write to destination file %s: error=%d\r\n", path, ret);
                exit(1);
            }
            c = fgetc(srcf);
        }

        // Close destination file
        ret = lfs_file_close(&lfs, &dstf);
        if (ret < 0) {
            fprintf(stderr, "can't close destination file %s: error=%d\r\n", path, ret);
            exit(1);
        }

        // Close source file
        fclose(srcf);
    }
}
static void skip_direct(char *curr_path, struct dirent *ent, char *src);

static void compact(char *src)
{
    DIR *dir;
    struct dirent *ent;
    char curr_path[PATH_MAX];

    dir = opendir(src);
    if (dir) {
        while ((ent = readdir(dir))) {
            // Skip . and .. directories
            skip_direct(curr_path, ent, src);
        }

        closedir(dir);
    }
}
static void skip_direct(char *curr_path, struct dirent *ent, char *src)
{
    if ((strcmp(ent->d_name, ".") != 0) && (strcmp(ent->d_name, "..") != 0)) {
        // Update the current path
        strcpy(curr_path, src);
        strcat(curr_path, "/");
        strcat(curr_path, ent->d_name);

        if (ent->d_type == DT_DIR) {
            create_dir(curr_path);
            compact(curr_path);
        } else if (ent->d_type == DT_REG) {
            create_file(curr_path);
        }
    }
}
void usage(void)
{
    int ret = 0;
    ret = fprintf(stdout,
                  "usage: mklfs -c <pack-dir> -b <block-size> -r <read-size> -p <prog-size> -s <filesystem-size> -n "
                  "<context> -l <lookahead_size> -e <cache_size> -k <block_cycles> -i <image-file-path>\r\n");
}

static int is_number(const char *s)
{
    const char *c = s;

    while (*c) {
        if ((*c < '0') || (*c > '9')) {
            return 0;
        }
        c++;
    }

    return 1;
}

static int is_hex(const char *s)
{
    const char *c = s;

    if (*c++ != '0') {
        return 0;
    }

    if (*c++ != 'x') {
        return 0;
    }

    while (*c) {
        if (((*c < '0') || (*c > '9')) && ((*c < 'A') || (*c > 'F')) && ((*c < 'a') || (*c > 'f'))) {
            return 0;
        }
        c++;
    }

    return 1;
}

static int to_int(const char *s)
{
    if (is_number(s)) {
        return atoi(s);
    } else if (is_hex(s)) {
        return (int)strtol(s, NULL, 0x10);
    }

    return -1;
}

int main(int argc, char **argv)
{
    char *src = NULL;   // Source directory
    char *dst = NULL;   // Destination image
    int c;              // Current option
    int block_size = 0; // Block size
    int read_size = 0;  // Read size
    int prog_size = 0;  // Prog size
    int fs_size = 0;    // File system size
    int err;
    int part_no = 0;    // context
    int blk_cycles = 0; // block_cycles
    int la_size = 0;    // lookahead_size
    int ca_size = 0;    // cache_size

    while ((c = getopt(argc, argv, "c:i:b:p:r:s:n:l:e:k:")) != -1) {
        switch (c) {
            case 'c':
                src = optarg;
                break;

            case 'i':
                dst = optarg;
                break;

            case 'b':
                block_size = to_int(optarg);
                break;

            case 'p':
                prog_size = to_int(optarg);
                break;

            case 'r':
                read_size = to_int(optarg);
                break;

            case 's':
                fs_size = to_int(optarg);
                break;

            case 'n':
                part_no = to_int(optarg);
                break;

            case 'l':
                la_size = to_int(optarg);
                break;

            case 'e':
                ca_size = to_int(optarg);
                break;

            case 'k':
                blk_cycles = to_int(optarg);
                break;
            default:
                break;
        }
    }

    if ((src == NULL) || (dst == NULL) || (block_size <= 0) || (prog_size <= 0) || (read_size <= 0) || (fs_size <= 0)) {
        usage();
        exit(1);
    }

    // Mount the file system
    cfg.read = lfs_read;
    cfg.prog = lfs_prog;
    cfg.erase = lfs_erase;
    cfg.sync = lfs_sync;

    cfg.block_size = block_size;
    cfg.read_size = read_size;
    cfg.prog_size = prog_size;
    cfg.block_count = fs_size / cfg.block_size;
    cfg.lookahead_size = la_size;
    cfg.context = (void *)part_no;
    cfg.block_cycles = blk_cycles;
    cfg.cache_size = ca_size;

    data = calloc(1, fs_size);
    if (!data) {
        fprintf(stderr, "no memory for mount\r\n");
        return -1;
    }

    err = lfs_format(&lfs, &cfg);
    if (err < 0) {
        fprintf(stderr, "format error: error=%d\r\n", err);
        return -1;
    }

    err = lfs_mount(&lfs, &cfg);
    if (err < 0) {
        fprintf(stderr, "mount error: error=%d\r\n", err);
        return -1;
    }

    char *last_dir = NULL;
    last_dir = strrchr(src, '/');
    if (last_dir) {
        last_dir++;
        compact(last_dir);
    } else {
        compact(src);
    }

    FILE *img = fopen(dst, "wb+");

    if (!img) {
        fprintf(stderr, "can't create image file: errno=%d (%s)\r\n", errno, strerror(errno));
        return -1;
    }

    int ret;
    ret = fwrite(data, 1, fs_size, img);

    ret = fclose(img);

    return 0;
}
